﻿//===============================================================================
// Microsoft patterns & practices Enterprise Library Contribution
// Query Application Block
//===============================================================================

using System;
using System.Linq;
using System.Data;
using System.Xml.Linq;
using System.Globalization;
using System.Collections.Generic;
using EntLibContrib.Query.Configuration;
using EntLibContrib.Query.Properties;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;

namespace EntLibContrib.Query.Commands
{
	/// <summary>
	/// Simple abstract class to hold the components of an xml file command
	/// </summary>
    [ConfigurationElementType(typeof(XmlFileCommandData))]
    public class XmlFileCommand : FileCommand
	{
		private const string rootElementSuffix = "Collection";

		private string element;
		private string rootElement;

		/// <summary>
		/// Gets the element.
		/// </summary>
		/// <value>The element.</value>
		public string Element
		{
			get { return element; }
			set
			{
				if (value != null)
				{
					element = value;
					rootElement = element + rootElementSuffix;
				}
			}
		}
		
        /// <summary>
		/// Initializes a new instance of the <see cref="XmlFileCommand"/> class.
		/// </summary>
		public XmlFileCommand()
		{
		}

        /// <summary>
        /// Initializes a new instance of the <see cref="XmlFileCommand"/> class.
        /// </summary>
        /// <param name="element">The element.</param>
        /// <param name="dataFile">The data file.</param>
        /// <param name="parameterSet">The parameter set.</param>
        public XmlFileCommand(string element, string dataFile, IParameterSet parameterSet)
            : this(element, dataFile, parameterSet.Parameters)
        {
        }

	    /// <summary>
		/// Initializes a new instance of the <see cref="XmlFileCommand"/> class.
		/// </summary>
		/// <param name="element">The element.</param>
		/// <param name="dataFile">The data file.</param>
		/// <param name="parameters">The parameters.</param>
		public XmlFileCommand(string element, string dataFile, ParameterDictionary parameters)
			: base(dataFile, parameters)
		{
			ValidateAndStoreParameters(element);
		}
		
        /// <summary>
		/// Executes a read action.
		/// </summary>
		/// <returns>
		/// a DataSet containing the results of the read action
		/// </returns>
		/// <remarks>searches can only be made on attributes of the given element type</remarks>
		public override DataSet ExecuteForRead()
		{
			// read the xml document
			XDocument document = XDocument.Load(DataFile, LoadOptions.SetBaseUri | LoadOptions.SetLineInfo);
			// filter the target elements
			IEnumerable<XElement> elements = document.Descendants(Element);
			// perform a search - multiple parameters are always AND comparisions of attributes of the given element
			if ((elements.Count<XElement>() > 0) && (Parameters != null))
			{
				foreach (KeyValuePair<string, IParameter> parameter in Parameters)
				{
					elements = elements.Where(a => (((string)a.Attribute(parameter.Key)).ToLower(CultureInfo.CurrentCulture)) == parameter.Value.Value.ToString().ToLower(CultureInfo.CurrentCulture));
				}
			}
			// build the filtered document
			XDocument filteredDocument = new XDocument(new XElement(rootElement, elements));
			// create the return dataset
			DataSet results = new DataSet(rootElement);
			results.Locale = CultureInfo.CurrentCulture;
			// read in the xml
			results.ReadXml(filteredDocument.CreateReader());
			// return the dataset
			return results;
		}

		/// <summary>
		/// Executes a write action.
		/// </summary>
		/// <remarks>
		/// 	<para>Only values of child elements of the given element type can be updated.</para>
		/// 	<para>All searches are made based on the attribute values of the given element type.</para>
		/// 	<para>Only updates can be made, all create and delete operations must be done using a custom command.</para>
		/// </remarks>
		public override void ExecuteForWrite()
		{
			// read the xml document
			var document = XDocument.Load(DataFile, LoadOptions.SetBaseUri | LoadOptions.SetLineInfo);
			// filter the target elements
			var elements = document.Descendants(Element);
			// separate the parameters into search parameters (matching attributes) and update parameters (matching elements)
			if ((elements.Count<XElement>() > 0) && (Parameters != null))
			{
				var searchParameters = new ParameterDictionary();
				var updateParameters = new ParameterDictionary();
				foreach (KeyValuePair<string, IParameter> parameter in Parameters)
				{
					bool matched = false;
					// use the first element as a template
					foreach (XAttribute attribute in elements.First<XElement>().Attributes())
					{
						if (attribute.Name.LocalName.ToLower(CultureInfo.CurrentCulture) == parameter.Key.ToLower(CultureInfo.CurrentCulture))
						{
							matched = true;
							break;
						}
					}
					// if the parameter matches an attribute then it is a search parameter otherwise it is an update parameter
					if (matched)
						searchParameters.Add(parameter.Key, parameter.Value);
					else
						updateParameters.Add(parameter.Key, parameter.Value);
				}
				// perform a search - multiple parameters are always AND comparisions of attributes of the given element
				foreach (KeyValuePair<string, IParameter> parameter in searchParameters)
				{
					elements = elements.Where(a => (((string)a.Attribute(parameter.Key)).ToLower(CultureInfo.CurrentCulture)) == parameter.Value.Value.ToString().ToLower(CultureInfo.CurrentCulture));
				}
				// update the resulting matched elements
				if (elements.Count<XElement>() > 0)
				{
					foreach (XElement xElement in elements)
					{
						foreach (KeyValuePair<string, IParameter> parameter in updateParameters)
						{
							xElement.SetElementValue(parameter.Key, parameter.Value.Value);
						}
					}
					// save the xml document
					document.Save(DataFile);
				}
			}
		}

		/// <summary>
		/// Validates the and store parameters.
		/// </summary>
		/// <param name="elementName">Name of the element.</param>
		private void ValidateAndStoreParameters(string elementName)
		{
			// check for a null or empty element
			if (string.IsNullOrEmpty(elementName))
				throw new ArgumentNullException("elementName", InternalResources.ExceptionXmlFileCommandElementNullOrEmpty);
			// store the element
			this.Element = elementName;
		}
	}
}
